배열 고차 함수
#
고차 함수란 ?고차 함수는(Higher order function)는 함수를 인자로 전달 받거나, 함수를 결과로 반환하는 함수이다. 고차 함수는 인자로 받은 함수를 필요한 시점에 호출하거나, 클로저
를 생성하여 반환한다. 자바스크립트의 함수는 일급 객체
이므로 값처럼 인자로 전달할 수 있으며, 반환할 수도 있다.
클로저
는 자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수이다. 클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지할 때 이다.
일급 객체(first-class object)
란 생성, 대입, 연산, 인자 또는 반환값으로서의 전달 등 프로그래밍 언어의 기본적 조작을 제한없이 사용할 수 있는 대상을 의미한다.
#
고차 함수 특징고차 함수는 외부 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍
에 기반을 두고 있다.
함수형 프로그래밍
은 순수 함수(pure function)와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고, 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다. 조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 하여 가독성을 해치고, 변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있기 때문이다.
함수형 프로그래밍은 결국 순수 함수를 통해 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 한 방법이라고 할 수있다.
#
1. forEach- forEach 메소드는 for 문 대신 사용할 수 있다.
- 배열을 순회하며 배열의 각 요소에 대해서 인자로 주어진 콜백함수를 실행한다. 반환 값은 undefined이다
- 콜백 함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, forEach 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
- forEach 메소드는 원본 배열(this)을 변경하지 않는다. 하지만 콜백 함수는 원본 배열(this)을 변경할 수는 있다.
- forEach 메소드는 for문과는 달리 break 문을 사용할 수 없다. 다시말해, 배열의 모든 요소를 순회하며 중간에 순회를 중단할 수 없다.
- forEach 메소드는 for 문에 비해 성능이 좋지는 않다. 하지만 for 문보다 가독성이 좋으므로 적극 사용을 권장한다.
const numbers = [1, 2, 3];let pows = [];
// for 문으로 순회for (let i = 0; i < numbers.length; i++) { pows.push(numbers[i] ** 2);}
console.log(pows); // [1, 4, 9]pows = [];
// forEach 메소드로 순회 (화살표 함수)numbers.forEach((item) => pows.push(item ** 2));
console.log(pows); // [1, 4, 9]pows = [];
// forEach 메소드로 순회 (일반 함수)numbers.forEach(function (item) { pows.push(item ** 2);});
console.log(pows); // [1, 4, 9]
const numbers = [1, 3, 5, 7, 9];let total = 0;
// forEach 메소드는 인수로 전달한 보조 함수를 호출하면서// 3개 (배열 요소의 값, 요소 인덱스, this)의 인수를 전달한다.// 배열의 모든 요소를 순회하며 합산한다.
numbers.forEach((item, index, self) => (total += item));
// reduce를 사용해도 위와 동일한 결과를 얻을 수 있다.// total = numbers.reduce((pre, cur) => pre + cur);
console.log(total); // 25
const numbers = [1, 2, 3, 4];
// forEach 메소드는 원본 배열(this)을 변경하지 않는다. 하지만 콜백 함수는 원본 배열(this)을 변경할 수는 있다.// 원본 배열을 직접 변경하려면 콜백 함수의 3번째 인자(this)를 사용한다.
numbers.forEach((item, index, self) => (self[index] = Math.pow(item, 2)));
console.log(numbers); //[1, 4, 9, 16]
// forEach 메소드는 for 문과는 달리 break 문을 사용할 수 없다.
const numbers = [1, 2, 3];numbers.forEach((item, index, self) => { if (item > 1) break; // SyntaxError: Illegal break statement})
#
2. map- 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백 함수의 반환값(결과 값)으로 새로운 배열을 생성하여 반환한다. 이때 원본 배열은 변경되지 않는다.
- forEach 메소드는 배열을 순회하며 요소 값을 참조하여 무언가를 하기 위한 함수이며, map 메소드는 배열을 순회하며 요소 값을 다른 값으로 맵핑 하기 위한 함수이다.
- 콜백 함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, map 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
const numbers = [1, 4, 9];
// 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백함수를 실행const roots = numbers.map((item) => Math.sqrt(item));
// 위 코드의 축약 표현은 아래와 같다.// const roots = numbers.map(Math.sqrt);
// map 메소드는 새로운 배열을 반환한다.console.log(roots); // [1,2,3]// map 메소드는 원본 배열은 변경하지 않는다.console.log(numbers); // [1,4,9]
//콜백 함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, map 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
const numbers = [1, 4, 9];
const result = numbers.map((item, index, self) => item + index);
#
3. filter- filter 메소드를 사용하면 if 문을 대체할 수 있다.
- 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백함수의 실행 결과가 true인 배열 요소의 값만을 추출한 새로운 배열을 반환한다.
- 배열에서 특정 케이스만 필터링 조건으로 추출하여 새로운 배열을 만들고 싶을 때 사용한다. 이때 원본 배열은 변경되지 않는다.
- 콜백 함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, filter 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
const numbers = [1, 2, 3, 4, 5];
const result = numbers.filter((item, index, self) => item % 2); // 홀수만을 필터링한다
#
4. reduce- 배열을 순회하며, 각 요소에 대하여 이전의 콜백함수 실행 반환값을 전달하여 콜백함수를 실행하고, 그 결과를 반환한다.
const arr = [1, 2, 3, 4, 5];
/*
previousValue : 이전 콜백의 반환값 currentValue : 배열 요소의 값 currentIndex : 인덱스 self : 메소드를 호출한 배열, 즉 this
*/// 합산const sum = arr.reduce((previousValue, currentValue, currentIndex, self) => { console.log( `${previousValue} + ${currentValue} = ${previousValue + currentValue}` ); return previousValue + currentValue; // 결과는 다음 콜백의 첫번째 인자로 전달된다.});
console.log(sum); // 15: 1~5까지의 합/*1: 1+2=32: 3+3=63: 6+4=104: 10+5=1515*/
// 최댓값 구하기const max = arr.reduce((pre, cur) => (pre > cur ? pre : cur));
reduce의 두번째 인수로 초기값을 전달할 수 있다. 이 값은 콜백 함수에 최초로 전달된다.
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((pre, cur) => { return pre + cur;}, 5);
// 위에 코드를 간단히 쓰면// const sum = arr.reduce((pre, cur) => pre + cur, 5);
console.log(sum); // 20// 5 + 1 => 6 + 2 => 8 + 3 => 11 + 4 => 15 + 5
객체의 프로퍼티 값을 합산하는 경우를 생각해 보자.
const products = [ { id: 1, price: 100 }, { id: 2, price: 200 }, { id: 3, price: 300 },];
// 프로퍼티 값을 합산const priceSum = products.reduce((pre, cur) => pre.price + cur.price); // 숫자 값이 두번째 콜백 함수 호출의 인자로 전달된다. 이때 pre.price는 undefinded이다.
console.log(priceSum); // NaN
이처럼 객체의 프로퍼티 값을 합산하는 경우에는 반드시 초기값을 전달해야 한다.
const products = [ { id: 1, price: 100 }, { id: 2, price: 200 }, { id: 3, price: 300 },];
// 프로퍼티 값을 합산const priceSum = products.reduce((pre, cur) => pre + cur.price, 0);
console.log(priceSum); // 600
reduce로 빈 배열을 호출하면 에러가 발생한다.
const arr = [];
const sum = arr.reduce((pre, cur) => pre + cur);// TypeError: Reduce of empty array with no initial value
초기값을 전달하면 에러를 회피할 수 있다. 따라서 reduce를 호출할 때는 언제나 초기값을 전달하는 것이 보다 안전하다
const arr = [];
const sum = arr.reduce((pre, cur) => pre + cur, 0);
console.log(sum); // 0
#
5. some- 배열 내 일부 요소가 콜백 함수의 테스트를 통과하는지 확인하여 그 결과를 boolean으로 반환한다.
- 콜백함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
// 배열 내 요소 중 10보다 큰 값이 1개 이상 존재하는지 확인let res = [2, 5, 8, 1, 4].some((item) => item > 10);console.log(res); // false
res = [12, 5, 8, 1, 4].some((item) => item > 10);console.log(res); // true
// 배열 내 요소 중 특정 값이 1개 이상 존재하는지 확인res = ['apple', 'banana', 'mango'].some((item) => item === 'banana');console.log(res); // true
#
6. every- 배열 내 모든 요소가 콜백함수의 테스트를 통과하는지 확인하여 그 결과를 boolean으로 반환한다.
- 콜백함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
// 배열 내 모든 요소가 10보다 큰 값인지 확인let res = [21, 15, 89, 1, 44].every((item) => item > 10);console.log(res); // false
res = [21, 15, 89, 100, 44].every((item) => item > 10);console.log(res); // true
#
7. find- 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백함수를 실행하여 그 결과가 참인 첫번째 요소를 반환한다.콜백함수의 실행 결과가 참인 요소가 존재하지 않는다면
undefined
를 반환한다. - 콜백함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
filter
는 콜백함수의 실행 결과가 true인 배열 요소의 값만을 추출한 새로운 배열을 반환한다. 따라서filter
의 반환 값은 언제나 배열이다. 하지만 find는 콜백함수를 실행하여 그 결과가 참인 첫번째 요소를 반환하므로 find의 결과값은 해당 요소 값이다.
const users = [ { id: 1, name: 'Lee' }, { id: 2, name: 'Kim' }, { id: 2, name: 'Choi' }, { id: 3, name: 'Park' },];
// 콜백함수를 실행하여 그 결과가 참인 첫번째 요소를 반환한다.let result = users.find((item) => item.id === 2);
// 조건이 true인 값들로 배열을 반환하는 filter와 다르게 find는 요소를 반환한다.console.log(result); // {id: 2, name: "Kim"}
#
8. findIndex- 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백함수를 실행하여 그 결과가 참인 첫번째 요소의 인덱스를 반환한다. 콜백함수의 실행 결과가 참인 요소가 존재하지 않는다면 -1을 반환한다.
- 콜백함수의 매개변수를 통해 배열 요소의 값, 요소 인덱스, 메소드를 호출한 배열, 즉 this를 전달 받을 수 있다.
const users = [ { id: 1, name: 'Lee' }, { id: 2, name: 'Kim' }, { id: 2, name: 'Choi' }, { id: 3, name: 'Park' },];
// 콜백함수를 실행하여 그 결과가 참인 첫번째 요소의 인덱스를 반환한다.let result = users.findIndex((item) => item.id === 3);
console.log(result); // 3